﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Logging;
using StyleMVVM.Utilities;
using StyleMVVM.View.Regions;
using StyleMVVM.LanguageExtensions;
#if !DOT_NET
using Windows.Foundation.Collections;
#endif
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

#else
using System.Windows;

#endif

namespace StyleMVVM.View
{
	/// <summary>
	/// This class is to help bind XAML and the Region Manager.
	/// </summary>
	public sealed class Region : DependencyObject
	{
		#region Private Protected Fields

		private static readonly string supplemental = typeof(Region).FullName;
		private WeakReference elementReference;
		private WeakReference currentIRegion;

		#endregion

		#region List Property

		public static RegionCollection GetList(DependencyObject obj)
		{
			return (RegionCollection)obj.GetValue(InternalListProperty);
		}

		public static void SetList(DependencyObject obj, RegionCollection value)
		{
			obj.SetValue(InternalListProperty, value);
		}

		public static DependencyProperty ListProperty
		{
			get { return InternalListProperty; }
		}

		// Using a DependencyProperty as the backing store for List.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalListProperty =
			DependencyProperty.RegisterAttached("List",
			                                    typeof(RegionCollection),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, ListChanged));

		#endregion

		#region Name Property

		public string FullName
		{
			get { return CreatefullName(GetName(this), GetInstance(this)); }
		}

		public string RegionName
		{
			get { return GetName(this); }
			set { SetName(this, value); }
		}

		public static string GetName(DependencyObject obj)
		{
			return (string)obj.GetValue(RegionNameProperty);
		}

		public static void SetName(DependencyObject obj, string value)
		{
			obj.SetValue(RegionNameProperty, value);
		}

		public static DependencyProperty RegionNameProperty
		{
			get { return InternalRegionNameProperty; }
		}

		// Using a DependencyProperty as the backing store for Name.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalRegionNameProperty =
			DependencyProperty.RegisterAttached("Name",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, NameChanged));

		#endregion

		#region Home Property

		public string RegionHome
		{
			get { return GetHome(this); }
			set { SetHome(this, value); }
		}

		public static string GetHome(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalHomeProperty);
		}

		public static void SetHome(DependencyObject obj, string value)
		{
			obj.SetValue(InternalHomeProperty, value);
		}

		public static DependencyProperty HomeProperty
		{
			get { return InternalHomeProperty; }
		}

		// Using a DependencyProperty as the backing store for Home.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalHomeProperty =
			DependencyProperty.RegisterAttached("Home",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, HomeChanged));

		#endregion

		#region Instance Property

		public string Instance
		{
			get { return GetInstance(this); }
			set { SetInstance(this, value); }
		}

		public static string GetInstance(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalInstanceProperty);
		}

		public static void SetInstance(DependencyObject obj, string value)
		{
			obj.SetValue(InternalInstanceProperty, value);
		}

		public static DependencyProperty InstanceProperty
		{
			get { return InternalInstanceProperty; }
		}

		// Using a DependencyProperty as the backing store for Instance.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalInstanceProperty =
			DependencyProperty.RegisterAttached("Instance",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, InstanceChanged));

		#endregion

		#region Adapter Property

		public IRegionAdapter RegionAdapter
		{
			get { return GetAdapter(this); }
			set { SetAdapter(this, value); }
		}

		public static IRegionAdapter GetAdapter(DependencyObject obj)
		{
			return (IRegionAdapter)obj.GetValue(InternalAdapterProperty);
		}

		public static void SetAdapter(DependencyObject obj, IRegionAdapter value)
		{
			obj.SetValue(InternalAdapterProperty, value);
		}

		public static DependencyProperty AdapterProperty
		{
			get { return InternalRegionNameProperty; }
		}

		// Using a DependencyProperty as the backing store for Adapter.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalAdapterProperty =
			DependencyProperty.RegisterAttached("Adapter",
			                                    typeof(IRegionAdapter),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, AdapterChanged));

		#endregion

		#region Element Property

		public FrameworkElement Element
		{
			get
			{
				if (elementReference != null)
				{
					return elementReference.Target as FrameworkElement;
				}

				return null;
			}
			set
			{
				FrameworkElement oldElement = null;

				if (elementReference != null)
				{
					oldElement = elementReference.Target as FrameworkElement;
				}

				elementReference = new WeakReference(value);

				if (!DesignMode.IsEnabled)
				{
					if (oldElement != null)
					{
						value.Loaded -= ValueOnLoaded;
						value.Unloaded -= ValueOnUnloaded;
					}

					if (value != null)
					{
						value.Loaded += ValueOnLoaded;
						value.Unloaded += ValueOnUnloaded;
					}
				}

				Connect();

				GoHome();
			}
		}

		#endregion

		#region GoHomeOn

		public static string GetGoHomeOn(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalGoHomeOnProperty);
		}

		public static void SetGoHomeOn(DependencyObject obj, string value)
		{
			obj.SetValue(InternalGoHomeOnProperty, value);
		}

		public static DependencyProperty GoHomeOnProperty
		{
			get { return InternalGoHomeOnProperty; }
		}

		// Using a DependencyProperty as the backing store for GoHomeOn.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalGoHomeOnProperty =
			DependencyProperty.RegisterAttached("GoHomeOn",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, GoHomeOnChanged));

		#endregion

		#region GoForwardOn

		public static string GetGoForwardOn(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalGoForwardOnProperty);
		}

		public static void SetGoForwardOn(DependencyObject obj, string value)
		{
			obj.SetValue(InternalGoForwardOnProperty, value);
		}

		public static DependencyProperty GoForwardOnProperty
		{
			get { return InternalGoForwardOnProperty; }
		}

		// Using a DependencyProperty as the backing store for GoForwardOn.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalGoForwardOnProperty =
			DependencyProperty.RegisterAttached("GoForwardOn",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, GoForwardOnChanged));

		#endregion

		#region GoBackOn

		public static string GetGoBackOn(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalGoBackOnProperty);
		}

		public static void SetGoBackOn(DependencyObject obj, string value)
		{
			obj.SetValue(InternalGoBackOnProperty, value);
		}

		public static DependencyProperty GoBackOnProperty
		{
			get { return InternalGoBackOnProperty; }
		}

		// Using a DependencyProperty as the backing store for GoBackOn.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalGoBackOnProperty =
			DependencyProperty.RegisterAttached("GoBackOn",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, GoBackOnChanged));

		#endregion

		#region NavigateTo

		public static string GetNavigateTo(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalNavigateToProperty);
		}

		public static void SetNavigateTo(DependencyObject obj, string value)
		{
			obj.SetValue(InternalNavigateToProperty, value);
		}

		public static DependencyProperty NavigateToProperty
		{
			get { return InternalNavigateToProperty; }
		}

		// Using a DependencyProperty as the backing store for NavigateTo.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalNavigateToProperty =
			DependencyProperty.RegisterAttached("NavigateTo",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, NavigateToChanged));

		#endregion

		#region NavigateOn

		public static string GetNavigateOn(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalNavigateOnProperty);
		}

		public static void SetNavigateOn(DependencyObject obj, string value)
		{
			obj.SetValue(InternalNavigateOnProperty, value);
		}

		public static DependencyProperty NavigateOnProperty
		{
			get { return InternalNavigateOnProperty; }
		}

		// Using a DependencyProperty as the backing store for NavigateOn.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalNavigateOnProperty =
			DependencyProperty.RegisterAttached("NavigateOn",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, NavigateOnChanged));

		#endregion

		#region NavigatingRegion

		public static string GetNavigatingRegion(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalNavigatingRegionProperty);
		}

		public static void SetNavigatingRegion(DependencyObject obj, string value)
		{
			obj.SetValue(InternalNavigatingRegionProperty, value);
		}

		public static DependencyProperty NavigatingRegionProperty
		{
			get { return InternalNavigatingRegionProperty; }
		}

		// Using a DependencyProperty as the backing store for NavigatingRegion.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalNavigatingRegionProperty =
			DependencyProperty.RegisterAttached("NavigatingRegion",
			                                    typeof(string),
			                                    typeof(Region),
			                                    new PropertyMetadata(null, NavigatingRegionChanged));

		#endregion

		#region Navigation Handlers

		private static void GoBackOnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

			try
			{
				RegionNavigationHelper helper = RegionNavigationHelper.GetOrCreateCurrentHelper(d);

				if (e.OldValue is string)
				{
					helper.DisconnectGoBackEvent(d, e.OldValue as string);
				}

				if (e.NewValue is string)
				{
					helper.ConnectGoBackEvent(d, e.NewValue as string);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while handling Back", supplemental, exp);
			}
		}

		private static void GoForwardOnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

			try
			{
				RegionNavigationHelper helper = RegionNavigationHelper.GetOrCreateCurrentHelper(d);

				if (e.OldValue is string)
				{
					helper.DisconnectGoForwardEvent(d, e.OldValue as string);
				}

				if (e.NewValue is string)
				{
					helper.ConnectGoForwardEvent(d, e.NewValue as string);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while handling Forward", supplemental, exp);
			}
		}

		private static void GoHomeOnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

			try
			{
				RegionNavigationHelper helper = RegionNavigationHelper.GetOrCreateCurrentHelper(d);

				if (e.OldValue is string)
				{
					helper.DisconnectGoHomeEvent(d, e.OldValue as string);
				}

				if (e.NewValue is string)
				{
					helper.ConnectGoHomeEvent(d, e.NewValue as string);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while handling Home", supplemental, exp);
			}
		}

		private static void NavigateToChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

			try
			{
				RegionNavigationHelper.GetOrCreateCurrentHelper(d);
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while handling NavigateTo", supplemental, exp);
			}
		}

		private static void NavigateOnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			try
			{
				RegionNavigationHelper helper = RegionNavigationHelper.GetOrCreateCurrentHelper(d);

				if (e.OldValue is string)
				{
					helper.DisconnectNavigateEvent(d, e.OldValue as string);
				}

				if (e.NewValue is string)
				{
					helper.ConnectNavigateEvent(d, e.NewValue as string);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while handling NavigateOn", supplemental, exp);
			}
		}

		private static void NavigatingRegionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			try
			{
				RegionNavigationHelper helper = RegionNavigationHelper.GetOrCreateCurrentHelper(d);
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while changing NavigatingRegion", supplemental, exp);
			}
		}

		#endregion

		#region Private Protected Members

		public bool IsContainerMaster { get; set; }

		private IRegion IRegion
		{
			get
			{
				if (currentIRegion != null)
				{
					return currentIRegion.Target as IRegion;
				}

				return null;
			}
		}

		private static void ListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

			FrameworkElement element = d as FrameworkElement;

			if (element != null)
			{
#if DOT_NET
				RegionCollection oldCollection = e.OldValue as RegionCollection;
				RegionCollection newCollection = e.NewValue as RegionCollection;

				if (oldCollection != null)
				{
					foreach (Region region in oldCollection)
					{
						region.Disconnect();
					}

					oldCollection.CollectionChanged -= CollectionOnCollectionChanged;
				}

				if (newCollection != null)
				{
					newCollection.Element = element;

					foreach (Region region in newCollection)
					{
						region.Element = element;
					}

					newCollection.CollectionChanged -= CollectionOnCollectionChanged;
				}
#else
				RegionCollection oldCollection = e.OldValue as RegionCollection;
				RegionCollection newCollection = e.NewValue as RegionCollection;

				if (oldCollection != null)
				{
					foreach (Region region in oldCollection)
					{
						region.Disconnect();
					}

					oldCollection.VectorChanged -= CollectionOnCollectionChanged;
				}

				if (newCollection != null)
				{
					newCollection.Element = element;

					foreach (Region region in newCollection)
					{
						region.Element = element;
					}

					newCollection.VectorChanged += CollectionOnCollectionChanged;
				}
#endif
			}
			else
			{
				throw new ArgumentException("Region Lists can only be assigned to Framework Elements");
			}
		}

		private static void NameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			try
			{
				DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

				FrameworkElement dFrameworkElement = d as FrameworkElement;

				if (dFrameworkElement != null)
				{
					Region currentRegion = GetOrCreateCurrentRegion(dFrameworkElement);

					if (currentRegion != null)
					{
						currentRegion.RegionHome = GetHome(dFrameworkElement);
						currentRegion.Instance = GetInstance(dFrameworkElement);
						currentRegion.RegionName = e.NewValue as string;
					}
				}
				else if (d is Region)
				{
					Region dRegion = d as Region;

					dRegion.Connect();

					if (DesignMode.IsEnabled)
					{
						dRegion.GoHome();
					}
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while setting region name: " + e.NewValue, supplemental, exp);
			}
		}

		private static void HomeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			try
			{
				DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

				FrameworkElement dFrameworkElement = d as FrameworkElement;

				if (dFrameworkElement != null)
				{
					Region currentRegion = GetOrCreateCurrentRegion(dFrameworkElement);

					currentRegion.Instance = GetInstance(dFrameworkElement);
					currentRegion.RegionName = GetName(dFrameworkElement);
					currentRegion.RegionHome = e.NewValue as string;
				}
				else if (d is Region)
				{
					Region dRegion = d as Region;

					dRegion.Connect();

					dRegion.GoHome();
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while setting region home: " + e.NewValue, supplemental, exp);
			}
		}

		private static void InstanceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			try
			{
				DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

				FrameworkElement dFrameworkElement = d as FrameworkElement;

				if (dFrameworkElement != null)
				{
					Region currentRegion = GetOrCreateCurrentRegion(dFrameworkElement);

					currentRegion.Instance = e.NewValue as string;
				}
				else if (d is Region)
				{
					Region dRegion = d as Region;

					dRegion.Connect();

					if (DesignMode.IsEnabled)
					{
						dRegion.GoHome();
					}
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while setting region Instance: " + e.NewValue, supplemental, exp);
			}
		}

		private static void AdapterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			try
			{
				DesignTimeBootstrapper.CreateDesignTimeBootstrapper();

				FrameworkElement dFrameworkElement = d as FrameworkElement;

				if (dFrameworkElement != null)
				{
					Region currentRegion = GetOrCreateCurrentRegion(dFrameworkElement);

					currentRegion.RegionAdapter = e.NewValue as IRegionAdapter;
				}
				else if (d is Region)
				{
					Region dRegion = d as Region;

					dRegion.Connect();

					if (DesignMode.IsEnabled)
					{
						dRegion.GoHome();
					}
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while setting region Adapter ", supplemental, exp);
			}
		}

		private static void ValueOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
		{
			RegionCollection collection = GetList(sender as DependencyObject);

			if (collection != null)
			{
				foreach (Region region in collection)
				{
					region.Disconnect();
				}
			}
		}

		private static void ValueOnLoaded(object sender, RoutedEventArgs routedEventArgs)
		{
			RegionCollection collection = GetList(sender as DependencyObject);

			if (collection != null)
			{
				foreach (Region region in collection)
				{
					region.Connect();
				}
			}
		}

		private string CreatefullName(string name, string context)
		{
			if (!string.IsNullOrEmpty(context))
			{
				return string.Concat(name, '|', context);
			}

			return name;
		}

		private void Connect()
		{
			FrameworkElement element = Element;
			string name = RegionName;
			string instance = Instance;
			string home = RegionHome;
			IRegionAdapter adapter = RegionAdapter;

			if (currentIRegion != null)
			{
				IRegion oldRegion = currentIRegion.Target as IRegion;

				if (oldRegion != null &&
				    (oldRegion.Name != name || oldRegion.Context != instance || adapter != null))
				{
					Disconnect();
				}
			}

			if (currentIRegion == null && element != null && !string.IsNullOrEmpty(name))
			{
				IRegionManager regionManager = Bootstrapper.Instance.Container.Locate<IRegionManager>();

				if (regionManager == null)
				{
					throw new Exception("Null region manager!");
				}

				IRegion newRegion = Bootstrapper.Instance.Container.Locate<IRegionManager>().CreateRegion(
					element, name, instance, adapter);

				if (newRegion != null)
				{
					newRegion.IsContainerMaster = IsContainerMaster;
					newRegion.Home = home;

					currentIRegion = new WeakReference(newRegion);
				}
				else
				{
					throw new Exception("Null region");
				}
			}
		}

		private void Disconnect()
		{
			if (currentIRegion != null)
			{
				IRegion oldRegion = currentIRegion.Target as IRegion;

				if (oldRegion != null)
				{
					oldRegion.Unhook();
				}

				currentIRegion = null;
			}
		}

#if DOT_NET
		private static void CollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			try
			{
				if (e.OldItems != null)
				{
					foreach (var item in e.OldItems)
					{
						Region oldRegion = item as Region;

						if (oldRegion != null)
						{
							oldRegion.Disconnect();
						}
					}
				}

				if (e.NewItems != null)
				{
					foreach (var item in e.NewItems)
					{
						Region newRegion = item as Region;

						if (newRegion != null)
						{
							newRegion.Connect();

							newRegion.GoHome();
						}
					}
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Excpetion thrown while handling collection changed", supplemental, exp);
			}
		}
#else
		private static void CollectionOnCollectionChanged(object sender,
		                                                  IVectorChangedEventArgs notifyCollectionChangedEventArgs)
		{
			IRegionCollectionChanged regionCollectionChanged = notifyCollectionChangedEventArgs as IRegionCollectionChanged;

			if (regionCollectionChanged != null)
			{
				try
				{
					if (regionCollectionChanged.OldRegions != null)
					{
						foreach (var oldRegion in regionCollectionChanged.OldRegions)
						{
							if (oldRegion != null)
							{
								oldRegion.Disconnect();
							}
						}
					}

					if (regionCollectionChanged.NewRegions != null)
					{
						foreach (var newRegion in regionCollectionChanged.NewRegions)
						{
							if (newRegion != null)
							{
								newRegion.Connect();

								newRegion.GoHome();
							}
						}
					}
				}
				catch (Exception exp)
				{
					Logger.Error("Excpetion thrown while handling collection changed", supplemental, exp);
				}
			}
		}
#endif

		private static Region GetOrCreateCurrentRegion(FrameworkElement d)
		{
			RegionCollection collection = null;

			try
			{
				collection = GetList(d);
			}
			catch (Exception)
			{
			}

			try
			{
				if (collection == null)
				{
					collection = new RegionCollection { Element = d };

					SetList(d, collection);
				}

				if (collection.Count < 1)
				{
					collection.Add(new Region());
				}

				return collection[0];
			}
			catch (Exception exp)
			{
				Logger.Error("Excpetion thrown while gettting CurrentRegion", supplemental, exp);
			}

			return null;
		}

		private async void GoHome()
		{
			FrameworkElement element = Element;
			IRegion region = null;
			string home = RegionHome;

			if (currentIRegion != null)
			{
				region = currentIRegion.Target as IRegion;
			}

			if (element != null && region != null && !string.IsNullOrEmpty(home))
			{
				region.Home = home;

				try
				{
					await region.GoHome();
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while going home", supplemental, exp);
				}
			}
		}

		#endregion
	}
}